const { promisify } = require('util');
const path = require('path');
const childProcess = require('child_process');
const fs = require('fs');
const isWsl = require('is-wsl');

const pAccess = promisify(fs.access);
const pExecFile = promisify(childProcess.execFile);

// Path to included `xdg-open`
const localXdgOpenPath = path.join(__dirname, 'xdg-open');

// Convert a path from WSL format to Windows format:
// `/mnt/c/Program Files/Example/MyApp.exe` → `C:\Program Files\Example\MyApp.exe`
const wslToWindowsPath = async path1 => {
    const { stdout } = await pExecFile('wslpath', ['-w', path1]);
    return stdout.trim();
};

module.exports = async (target, options) => {
    if (typeof target !== 'string') {
        throw new TypeError('Expected a `target`');
    }

    options = {
        wait: false,
        background: false,
        ...options
    };

    let command;
    let appArguments = [];
    const cliArguments = [];
    const childProcessOptions = {};

    if (Array.isArray(options.app)) {
        appArguments = options.app.slice(1);
        options.app = options.app[0];
    }

    if (process.platform === 'darwin') {
        command = 'open';

        if (options.wait) {
            cliArguments.push('--wait-apps');
        }

        if (options.background) {
            cliArguments.push('--background');
        }

        if (options.app) {
            cliArguments.push('-a', options.app);
        }
    } else if (process.platform === 'win32' || isWsl) {
        command = `cmd${isWsl ? '.exe' : ''}`;
        cliArguments.push('/c', 'start', '""', '/b');
        target = target.replace(/&/g, '^&');

        if (options.wait) {
            cliArguments.push('/wait');
        }

        if (options.app) {
            if (isWsl && options.app.startsWith('/mnt/')) {
                const windowsPath = await wslToWindowsPath(options.app);
                options.app = windowsPath;
            }

            cliArguments.push(options.app);
        }

        if (appArguments.length > 0) {
            cliArguments.push(...appArguments);
        }
    } else {
        if (options.app) {
            command = options.app;
        } else {
            // When bundled by Webpack, there's no actual package file path and no local `xdg-open`.
            const isBundled = !__dirname || __dirname === '/';

            // Check if local `xdg-open` exists and is executable.
            let exeLocalXdgOpen = false;
            try {
                await pAccess(localXdgOpenPath, fs.constants.X_OK);
                exeLocalXdgOpen = true;
            } catch (error) {
                //
            }

            const useSystemXdgOpen =
                process.versions.electron ||
                process.platform === 'android' ||
                isBundled ||
                !exeLocalXdgOpen;
            command = useSystemXdgOpen ? 'xdg-open' : localXdgOpenPath;
        }

        if (appArguments.length > 0) {
            cliArguments.push(...appArguments);
        }

        if (!options.wait) {
            // `xdg-open` will block the process unless stdio is ignored
            // and it's detached from the parent even if it's unref'd.
            childProcessOptions.stdio = 'ignore';
            childProcessOptions.detached = true;
        }
    }

    cliArguments.push(target);

    if (process.platform === 'darwin' && appArguments.length > 0) {
        cliArguments.push('--args', ...appArguments);
    }

    const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);

    if (options.wait) {
        return new Promise((resolve, reject) => {
            subprocess.once('error', reject);

            subprocess.once('close', exitCode => {
                if (exitCode > 0) {
                    reject(new Error(`Exited with code ${exitCode}`));
                    return;
                }

                resolve(subprocess);
            });
        });
    }

    subprocess.unref();

    return subprocess;
};
